/*
 * 础石OS	-- 设备树
 *
 * Copyright (C) 2022 国科础石(重庆)软件有限公司
 *
 * 作者: Hengxing Wu<wuhengxing@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */

#include <linux/compiler.h>
#include <dim-sum/devtree.h>
#include <dim-sum/errno.h>
#include <dim-sum/printk.h>
#include <dim-sum/string.h>
#include <asm/sections.h>

int dt_read_u32_index(const struct device_node *node, const char *name, u32 *out, int index)
{
	const u32 *pval;
	u32 size;

	if (!node || !name || !out || (index < 0))
		return -EINVAL;

	pval = dt_get_property(node, name);
	if (!pval)
		return -EINVAL;

	size = dt_property_size(node, name);
	if (size < ((index + 1) * sizeof(*out)))
		return -EINVAL;

	*out = be32_to_cpu(*(pval + index));

	return 0;
}

int dt_read_u32_array(const struct device_node *node, const char *name, u32 *out, size_t sz)
{
	const u32 *pval;
	u32 size;

	if (!node || !name || !out || (sz <= 0))
		return -EINVAL;

	pval = dt_get_property(node, name);
	if (!pval)
		return -EINVAL;

	size = dt_property_size(node, name);
	if (size < (sz * sizeof(*out)))
		return -EINVAL;

	while (sz--)
		*out++ = be32_to_cpu(*pval++);

	return 0;
}

/*
 *读取64位属性值
 */
int dt_read_u64_index(const struct device_node *node, const char *name, u64 *out, int index)
{
	const void *pval;
	u32 size;

	if (!node || !name || !out || (index < 0))
		return -EINVAL;

	pval = dt_get_property(node, name);
	if (!pval)
		return -EINVAL;

	size = dt_property_size(node, name);
	if (size < ((index + 1) * sizeof(*out)))
		return -EINVAL;

	*out = be64_to_cpu(*((const u64 *)pval + index));

	return 0;
}

int dt_read_u64_array(const struct device_node *node, const char *name, u64 *out, size_t sz)
{
	const u32 *pval;
	u32 size;

	if (!node || !name || !out || (sz <= 0))
		return -EINVAL;

	pval = dt_get_property(node, name);
	if (!pval)
		return -EINVAL;

	size = dt_property_size(node, name);
	if (size < (sz * sizeof(*out)))
		return -EINVAL;

	while (sz--) {
		*out++ = dt_read_number(pval, 2);
		pval += 2;
	}

	return 0;
}

int dt_read_string(const struct device_node *node, const char *propname, const char **out)
{
	const char *pval;

	if (!node || !propname || !out)
		return -EINVAL;

	pval = dt_get_property(node, propname);
	if (!pval)
		return -EINVAL;

	*out = pval;

	return 0;
}

int dt_match_string(struct device_node *node, const char *name, const char *string)
{
	struct property *pp = dt_find_property(node, name);
	const char *p, *end;
	size_t l;
	int i;

	if (!pp)
		return -EINVAL;

	if (!pp->value)
		return -ENODATA;

	p = pp->value;
	end = p + pp->len;

	for (i = 0; p < end; i++, p += l) {
		l = strlen(p) + 1;
		if (p + l > end)
			return -EILSEQ;
		if (strcmp(string, p) == 0)
			return i;
	}

	return -ENODATA;
}

int dt_count_strings(struct device_node *node, const char *name)
{
	struct property *pp = dt_find_property(node, name);
	size_t l = 0, total = 0;
	const char *p;
	int i;

	if (!pp)
		return -EINVAL;

	if (!pp->value)
		return -ENODATA;

	if (strnlen(pp->value, pp->len) >= pp->len)
		return -EILSEQ;

	p = pp->value;

	for (i = 0; total < pp->len; total += l, p += l, i++)
		l = strlen(p) + 1;

	return i;
}

int dt_read_string_index(struct device_node *node, const char *name, int index, const char **out)
{
	struct property *pp = dt_find_property(node, name);
	const char *p, *end;
	size_t l;
	int i;

	if (!pp || !out)
		return -EINVAL;

	if (!pp->value)
		return -ENODATA;

	if (index < 0)
		return -EILSEQ;

	p = pp->value;
	end = p + pp->len;

	for (i = 0; p < end; i++, p += l) {
		l = strlen(p) + 1;
		if (p + l > end)
			return -EILSEQ;

		if (i == index) {
			*out = p;
			return l - 1;
		}
	}

	return -ENODATA;
}

const u32 *dt_next_u32(struct property *pp, const u32 *cur, u32 *val)
{
	const void *next;

	if (!pp || !pp->value)
		return NULL;

	next = pp->value;

	if (cur)
		next = cur + sizeof(*cur);

	if (next >= pp->value + pp->len)
		return NULL;

	if (val)
		*val = be32_to_cpu(*(u32 *)next);

	return next;
}

const char *dt_next_string(struct property *pp, const char *cur)
{
	const char *first, *last, *next;

	if (!pp || !pp->value)
		return NULL;

	next = first = (const char *)pp->value;
	last = first + pp->len;

	if (cur)
		next = cur + strlen(cur) + 1;

	return next < last ? next : NULL;
}

